#include "LPR.h"
#include "DebugImage.h" // DBG
#include "OCREngine.h"

#include <highgui.h>
#include <cv.h>
#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
#include <queue>
using std::queue;
using std::pair;
#include <string.h> // DBG
using std::string; // DBG

// initialization of constants:
const double LPR::ms_minDigitPercent = 0.025;		// for eliminating segments unlikely to be digits
const double LPR::ms_maxDigitPercent = 0.15;		// for eliminating segments unlikely to be digits
const double LPR::ms_yellowPercentage = 0.36;	// for the binarify (histogram) [0.33 according to the article]

void LPR::compute (void) {
	binarify();
	segmentify();
	recognize();
}

void LPR::recognize (void) {
#ifdef _DEBUG
	//DebugImage dbg(m_gray, "segmentify() & recognize()"); // DBG

	CvFont font1, font2; // DBG
	cvInitFont( &font1, CV_FONT_HERSHEY_COMPLEX, 0.5, 0.5, 0.0, 2, CV_AA ); // DBG
	cvInitFont( &font2, CV_FONT_HERSHEY_COMPLEX, 0.5, 0.5, 0.0, 1, CV_AA ); // DBG
#endif

	for (t_segmentlist::iterator it = m_digits.begin() ; it != m_digits.end() ; ++it) {
		t_cord topleft((*it).first), bottomright((*it).second);

		IplImage *digit(cvCreateImage(cvSize(bottomright.second - topleft.second + 1, bottomright.first - topleft.first + 1), IPL_DEPTH_8U , 1));
		cvSetImageROI(m_gray, cvRect(topleft.second, topleft.first, digit->width, digit->height));
		cvCopy(m_gray, digit);
		cvResetImageROI(m_gray);
		int currdigit(OCREngine::instance()->identify(digit));

#ifdef _DEBUG
		string dig(""); // DBG
		dig += (char) currdigit + '0'; // DBG
		cvPutText(m_gray, dig.c_str(), cvPoint(topleft.second + 2, bottomright.first - 2) , &font1, cvScalarAll(ms_false)); // DBG
		cvPutText(m_gray, dig.c_str(), cvPoint(topleft.second + 2, bottomright.first - 2) , &font2, cvScalarAll(ms_true)); // DBG
		cvRectangle(m_gray, cvPoint(topleft.second, topleft.first), cvPoint(bottomright.second, bottomright.first), cvScalar(130)); // DBG
#endif

		m_result = m_result * 10 + currdigit;
		m_resultlist.push_back(currdigit);
		cvReleaseImage(&digit);
	}
}

void LPR::segmentify (void) {
	int visit((ms_true + ms_false) / 2); // a color to mark pixels we've visited.

	int row(m_gray->height / 2); // we can run on a single row in the middle as all digits intersect it
	for (int i(0) ; i < m_gray->width ; ++i) {
		uchar &pxl(CV_IMAGE_ELEM(m_gray, uchar, row, i));
		if (pxl == ms_true)
			add_object(m_gray, row, i, visit);
	}

	for(int i(0) ; i < m_gray->height ; ++i) { // correct the color given by the segmentation
		for(int j(0) ; j < m_gray->width ; ++j) {
			uchar &pxl(CV_IMAGE_ELEM(m_gray, uchar, i, j));
			if (pxl == visit)
				pxl = ms_true;
		}
	}
}

void LPR::add_object (IplImage* img, int row, int col, int visitcolor) { // basically, a bfs implementation
	queue<t_cord> q;
	q.push(t_cord(row,col));
	t_cord topleft(row,col), bottomright(row,col); // save bounding box
	while (!q.empty()) {
		t_cord curr (q.front()); 
		q.pop();
		uchar &pxl(CV_IMAGE_ELEM(m_gray, uchar, curr.first, curr.second));
		if (pxl != ms_true)
			continue;
		// (else)
		pxl = visitcolor;

		// getting the bounding box:
		if (curr.first < topleft.first)
			topleft.first = curr.first;
		if (curr.first > bottomright.first)
			bottomright.first = curr.first;
		if (curr.second < topleft.second)
			topleft.second = curr.second;
		if (curr.second > bottomright.second)
			bottomright.second = curr.second;

		// doing the bfs action, basically:
		if (curr.first > 0)
			q.push( t_cord(curr.first - 1, curr.second) );
		if (curr.first + 1 < img->height)
			q.push( t_cord(curr.first + 1, curr.second) );
		if (curr.second > 0)
			q.push( t_cord(curr.first, curr.second - 1) );
		if (curr.second + 1 < img->width)
			q.push( t_cord(curr.first, curr.second + 1) );
	}
	
	// percentage of the current object from the total of the plate:
	double percent( (double) ((bottomright.second - topleft.second) * (bottomright.first - topleft.first)) / (img->width * img->height));

	if (percent > ms_maxDigitPercent || percent < ms_minDigitPercent)	// let's get rid of non-digits.
		return;

	m_digits.push_back( t_rect(topleft, bottomright) );
}

void LPR::binarify (void) {
	assert(!m_gray);	// to make sure that it's null
	m_gray = cvCreateImage(cvGetSize(m_plate), IPL_DEPTH_8U , 1);
	cvCvtColor(m_plate , m_gray, CV_RGB2GRAY);

#ifdef _DEBUG
	//DebugImage dbg(m_gray, "binarify() & clean()");	// DBG
#endif

	calc_threshold();

	for(int i(0) ; i < m_gray->height ; ++i) {
		for(int j(0) ; j < m_gray->width ; ++j) {
			uchar &pxl(CV_IMAGE_ELEM(m_gray, uchar, i, j));
			pxl = pxl < m_threshold ? ms_true : ms_false;
		}
	}

	clean(); // possibly call it several times
}

void LPR::calc_threshold (void) {
	vector<int> histogram(256);
	for(int i(0) ; i < m_gray->height ; ++i)
		for(int j(0) ; j < m_gray->width ; ++j)
			++histogram[CV_IMAGE_ELEM(m_gray, uchar, i, j)];

	int sum(0), total(m_gray->width * m_gray->height);
	for (m_threshold = 0 ; m_threshold < histogram.size() ; ++m_threshold) {
		sum += histogram[m_threshold];
		if ((double)sum/total >= ms_yellowPercentage)
			break;
	}
}

void LPR::clean (void) {
	// possibly use (faster): 
	//cvSmooth( m_gray, m_gray, CV_MEDIAN, 3 );
	//return;

	// start of median filter (using 4 connectivity)

	IplImage* tmp(cvCloneImage(m_gray));
	for(int i(0) ; i < tmp->height ; ++i) {
		for(int j(0) ; j < tmp->width ; ++j) {
			int nTrue(0), nFalse(0);
			if (i > 0) {
				if (CV_IMAGE_ELEM(tmp, uchar, i-1, j) == ms_true)
					nTrue++;
				else
					nFalse++;
			}
			if (j > 0) {
				if (CV_IMAGE_ELEM(tmp, uchar, i, j-1) == ms_true)
					nTrue++;
				else
					nFalse++;
			}
			if (i < m_gray->height - 1) {
				if (CV_IMAGE_ELEM(tmp, uchar, i+1, j) == ms_true)
					nTrue++;
				else
					nFalse++;
			}
			if (j < m_gray->width - 1) {
				if (CV_IMAGE_ELEM(tmp, uchar, i, j+1) == ms_true)
					nTrue++;
				else
					nFalse++;
			}
			CV_IMAGE_ELEM(m_gray, uchar, i, j) = nTrue > nFalse ? ms_true : ms_false;
		}
	}

	cvReleaseImage(&tmp);

	// end of median filter
}

unsigned long LPR::result (void) const {
	return m_result;
}

std::list<int> LPR::resultList (void) const {
	return m_resultlist;
}
